package com.it.gmall.list.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.it.gmall.list.dao.GoodsDao;
import com.it.gmall.model.list.*;
import com.it.gmall.model.product.BaseCategoryView;
import com.it.gmall.model.product.BaseTrademark;
import com.it.gmall.model.product.SkuAttrValue;
import com.it.gmall.model.product.SkuInfo;
import com.it.gmall.product.client.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import springfox.documentation.spring.web.json.Json;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class ListServiceImpl implements ListService {
    @Autowired
    private GoodsDao goodsDao;
    @Autowired
    private ProductFeignClient productFeignClient;
    @Autowired
    private RedisTemplate redisTemplate;
    // 注入es
    @Autowired
    private RestHighLevelClient restHighLevelClient;

    // 下架商品
    @Override
    public void cancelSale(long skuId) {
        goodsDao.deleteById(skuId);
    }

    // 上架商品
    @Override
    public void onSale(long skuId) {
        Goods goods = new Goods();

        // 1.商品信息
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        goods.setId(skuId); // 商品id
        goods.setTitle(skuInfo.getSkuName()); // 商品名称
        goods.setDefaultImg(skuInfo.getSkuDefaultImg()); // 商品主图
        goods.setPrice(skuInfo.getPrice().doubleValue()); // 商品价格

        // 2.三级分类
        BaseCategoryView baseCategoryView = productFeignClient.getBaseCategoryView(skuInfo.getCategory3Id());
        goods.setCategory1Id(baseCategoryView.getCategory1Id());
        goods.setCategory1Name(baseCategoryView.getCategory1Name());
        goods.setCategory2Id(baseCategoryView.getCategory2Id());
        goods.setCategory2Name(baseCategoryView.getCategory2Name());
        goods.setCategory3Id(baseCategoryView.getCategory3Id());
        goods.setCategory3Name(baseCategoryView.getCategory3Name());

        // 3.品牌
        Long tmId = skuInfo.getTmId();
        BaseTrademark baseTrademark = productFeignClient.getBaseTrademark(tmId);
        goods.setTmId(tmId);
        goods.setTmName(baseTrademark.getTmName());
        goods.setTmLogoUrl(baseTrademark.getLogoUrl());

        // 4.创建时间
        Calendar calendar = Calendar.getInstance();
        goods.setCreateTime(calendar.getTime());

        // 5.属性
        List<SkuAttrValue> searchAttrList = productFeignClient.getSearchAttrList(skuId);
        List<SearchAttr> searchAttrs = searchAttrList.stream().map(skuAttrValue -> {
            SearchAttr searchAttr = new SearchAttr();
            searchAttr.setAttrId(skuAttrValue.getBaseAttrInfo().getId());
            searchAttr.setAttrName(skuAttrValue.getBaseAttrInfo().getAttrName());
            searchAttr.setAttrValue(skuAttrValue.getBaseAttrValue().getValueName());
            return searchAttr;
        }).collect(Collectors.toList());
        goods.setAttrs(searchAttrs);

        goodsDao.save(goods);
    }

    // 热度评分
    @Override
    public void incrHotScore(long skuId, Integer score) {
        /*为什么使用redis来放商品的热度评分？
        * 1.比如我们在浏览商品详情的时候+1
        * 在加入购物车的时候+2
        * 在结算的时候+5
        *
        * 那么每一个请求都会对es库进行加分操作
        * 在高并发的情况下 是不可取的*/


        // redis单线程 在原有的评分的基础上增加评分
        Double scoreRedis = redisTemplate.opsForZSet().incrementScore("hotKey", skuId, score);

        // 当redis中的数量累加到一定的值的时候 将其更新到es库中 比如：每加购十次的时候
        if(scoreRedis%10==0){
            // 根据id从redis中获取
            Goods goods = goodsDao.findById(skuId).get();
            goods.setHotScore(Math.round(scoreRedis)); // 设置热度评分
            goodsDao.save(goods); // 设置到redis
        }
    }

    // 搜索商品
    /*
     * 搜索商品的过程：
     * 1.根据页面传过来的数据 解析数据 制作DSL语句
     * 2.连接es索引库 根据上一步制作的DSL语句进行查询并返回查询结果
     * 3.将查询结果封装成页面所需要的数据格式并且返回查询结果
     * */
    @Override
    public SearchResponseVo searchItem(SearchParam searchParam) {
        System.out.println(searchParam);
        // 1.根据页面传过来的数据制作DSL语句
        SearchRequest searchRequest = this.parseSearchParam(searchParam);

        // 2.连接es库并返回搜索出来的数据
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 3.封装数据并返回
        SearchResponseVo searchResponseVo = this.packResponseData(searchResponse);

        searchResponseVo.setPageNo(searchParam.getPageNo());
        searchResponseVo.setPageSize(searchParam.getPageSize());
        //总页数  = （总条数 + 每页数 - 1）/每页数
        //            13        5
        long totalPages = (searchResponseVo.getTotal() + searchResponseVo.getPageSize() - 1) / searchResponseVo.getPageSize();
        searchResponseVo.setTotalPages(totalPages);



        // 返回数据
        return searchResponseVo;
    }

    /*搜索的返回数据封装*/
    private SearchResponseVo packResponseData(SearchResponse searchResponse) {
        SearchResponseVo searchResponseVo = new SearchResponseVo();

        // 聚合
        Aggregations aggregations = searchResponse.getAggregations();

        // 品牌列表-聚合
        ParsedLongTerms aggTmId = aggregations.get("aggTmId");
        List<SearchResponseTmVo> searchResponseTmVoList =
            aggTmId.getBuckets().stream().map(bucket -> {
                //key值 即id值
                SearchResponseTmVo responseTmVo = new SearchResponseTmVo();
                responseTmVo.setTmId(((Terms.Bucket) bucket).getKeyAsNumber().longValue());
                // 品牌名称
                ParsedStringTerms aggTermName = ((Terms.Bucket) bucket).getAggregations().get("aggTermName");
                responseTmVo.setTmName(aggTermName.getBuckets().get(0).getKeyAsString());
                // 品牌logo
                ParsedStringTerms aggLogoUrl = ((Terms.Bucket) bucket).getAggregations().get("aggLogoUrl");
                responseTmVo.setTmLogoUrl(aggLogoUrl.getBuckets().get(0).getKeyAsString());
                return responseTmVo;
            }).collect(Collectors.toList());
        searchResponseVo.setTrademarkList(searchResponseTmVoList);

        // 属性列表-聚合
        ParsedNested aggAttrs = aggregations.get("aggAttrs");
        ParsedLongTerms aggAttrId = aggAttrs.getAggregations().get("aggAttrId");
        List<SearchResponseAttrVo> attrsList =
            aggAttrId.getBuckets().stream().map((bucket)->{
                SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();
                // key值 id
                long attrId = bucket.getKeyAsNumber().longValue();
                searchResponseAttrVo.setAttrId(attrId);
                // attrName 名称
                ParsedStringTerms aggAttrName = bucket.getAggregations().get("aggAttrName");
                String attrName = aggAttrName.getBuckets().get(0).getKeyAsString();
                searchResponseAttrVo.setAttrName(attrName);
                // attrValueList list<String> 值集合
                ParsedStringTerms aggAttrValue = bucket.getAggregations().get("aggAttrValue");
                List<String> attrValueList =
                    aggAttrValue.getBuckets().stream().map((b)->{
                        String attrValue = b.getKeyAsString();
                        return attrValue;
                    }).collect(Collectors.toList());
                searchResponseAttrVo.setAttrValueList(attrValueList);
                return searchResponseAttrVo;
            }).collect(Collectors.toList());

        searchResponseVo.setAttrsList(attrsList);

        SearchHits hits = searchResponse.getHits();
        // 商品列表
        SearchHit[] searchHits = hits.getHits();
        List<Goods> goodsList =
            Arrays.stream(searchHits).map(h -> {
                // 从source字段中取值
                Goods goods = JSON.parseObject(h.getSourceAsString(),Goods.class);
                // 因为设置了高亮 因此将商品的标题换成高亮的名称
                Map<String, HighlightField> highlightFields = h.getHighlightFields();
                if(null!=highlightFields && highlightFields.size()>0){
                    HighlightField title = highlightFields.get("title");
                    String goodsTitle = title.getFragments()[0].toString();
                    goods.setTitle(goodsTitle);
                    System.out.println("商品标题："+goods.getTitle());
                }
                return goods;
            }).collect(Collectors.toList());
        searchResponseVo.setGoodsList(goodsList);

        // 分页数据
        long totalHits = hits.getTotalHits();
        searchResponseVo.setTotal(totalHits); // 总条数
        System.out.println("总条数："+totalHits);

        return searchResponseVo;
    }

    /*解析数据(制作DSL语句)*/
    private SearchRequest parseSearchParam(SearchParam searchParam) {
        SearchRequest searchRequest = new SearchRequest();
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

        // 1.搜索关键字
        String keyword = searchParam.getKeyword();
        if(!StringUtils.isEmpty(keyword)){
            boolQueryBuilder.must(QueryBuilders.matchQuery("title",keyword));
        }else {
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());//查询全部
        }

        // 2.三级分类
        Long category1Id = searchParam.getCategory1Id();
        if(null!=category1Id){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id",category1Id));
        }
        Long category2Id = searchParam.getCategory2Id();
        if(null!=category2Id){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id",category2Id));
        }
        Long category3Id = searchParam.getCategory3Id();
        if(null!=category3Id){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id",category3Id));
        }

        // 3.品牌(根据品牌id) 2:华为
        String trademark = searchParam.getTrademark();
        if(!StringUtils.isEmpty(trademark)){
            String[] split = StringUtils.split(trademark, ":");
            boolQueryBuilder.filter(QueryBuilders.termQuery("tmId",split[0]));
        }

        // 4.销售属性 props=id:value:name
        String[] props = searchParam.getProps();
        if(null!=props && props.length>0){
            // 父亲的 相当于attrs的中括号
            BoolQueryBuilder boolQueryFather = QueryBuilders.boolQuery();
            for (String prop : props) {
                String[] splitProp = prop.split(":");
                if(null!=splitProp&&splitProp.length==3){
                    // 每一个属性自己的
                    BoolQueryBuilder boolQuerySelf = QueryBuilders.boolQuery();
                    boolQuerySelf.must(QueryBuilders.termQuery("attrs.attrId",splitProp[0])); // id
                    boolQuerySelf.must(QueryBuilders.termQuery("attrs.attrValue",splitProp[1])); // value
                    // 把每一个属性加入到父亲中
                    boolQueryFather.must(QueryBuilders.nestedQuery("attrs",boolQuerySelf, ScoreMode.None));
                }
            }
            boolQueryBuilder.filter(boolQueryBuilder);
        }

        // 将查询条件放到builder构建中
        searchSourceBuilder.query(boolQueryBuilder);

        // 5.排序 ； 综合: 1:asc/desc ； 价格: 2:asc/desc
        String order = searchParam.getOrder();
        String sortField = "hotScore";
        if(!StringUtils.isEmpty(order)){
            String[] split = StringUtils.split(order, ":");
            // 页面指定了排序规则
            switch (split[0]){
                case "1":break;
                case "2":sortField="price";break;
            }
            searchSourceBuilder.sort(sortField,split[1].equalsIgnoreCase("asc")?SortOrder.ASC:SortOrder.DESC);
        }else{
            // 页面未指定排序规则 综合 降序进行排列
            searchSourceBuilder.sort(sortField, SortOrder.DESC);
        }

        // 6.分页
        Integer pageNo = searchParam.getPageNo(); // 当前页
        Integer pageSize = searchParam.getPageSize(); // 总记录数
        searchSourceBuilder.from((pageNo-1)*pageSize);
        searchSourceBuilder.size(pageSize);

        // 7.高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title").preTags("<font style='color:red'>").postTags("</font>");
        searchSourceBuilder.highlighter(highlightBuilder);

        // 8.聚合
        // 8.1 品牌聚合
        searchSourceBuilder.aggregation(AggregationBuilders.terms("aggTmId").field("tmId")
            .subAggregation(AggregationBuilders.terms("aggTermName").field("tmName"))
            .subAggregation(AggregationBuilders.terms("aggLogoUrl").field("tmLogoUrl")));
        // 8.2 平台属性聚合
        searchSourceBuilder.aggregation(AggregationBuilders.nested("aggAttrs","attrs")
            .subAggregation(AggregationBuilders.terms("aggAttrId").field("attrs.attrId")
            .subAggregation(AggregationBuilders.terms("aggAttrName").field("attrs.attrName"))
            .subAggregation(AggregationBuilders.terms("aggAttrValue").field("attrs.attrValue"))));

        System.out.println("DSL语句："+searchSourceBuilder);
        searchRequest.source(searchSourceBuilder);

        // 设置被用来搜索的数据库
        searchRequest.indices("goods");
        return searchRequest;
    }
}
